<!DOCTYPE html>
<html lang="zh_cn">
<head>
          <title>来玩魔王的咚</title>
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <meta charset="utf-8" />
        <!-- twitter card metadata -->
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="/images/mowang.png">
<meta name="twitter:site" content="">
<meta name="twitter:title" content="Go 方法和接口">
<meta name="twitter:description" content="<p>方法声明；指针接受者的方法；方法变量，方法表达式；接口类型；接口值：比较接口值，含有空指针的非空接口；类型断言；类型分支；使用接口的一些建议</p>">
        <!-- OG Tags -->
<meta property="og:url" content="/gopl-methods-interfaces.html"/>
<meta property="og:title" content="来玩魔王的咚 | Go 方法和接口" />
<meta property="og:description" content="<p>方法声明；指针接受者的方法；方法变量，方法表达式；接口类型；接口值：比较接口值，含有空指针的非空接口；类型断言；类型分支；使用接口的一些建议</p>" />
        <!-- favicon -->
        <link rel="icon" type="image/png" href="/images/mowang.png">
        <!-- moment.js for date formatting -->
        <script src="/theme/js/moment.js"></script>
        <!-- css -->
        <link rel="stylesheet" type="text/css" href="/theme/css/main.css" />
        <!-- 左边的menu，如果页面高度不够，就跟着滚动，否则文章分类显示不全 -->
        <link rel="stylesheet" type="text/css" href="/theme/css/mycss/menu.css" />
		<script>
			
                /*! grunt-grunticon Stylesheet Loader - v2.1.2 | https://github.com/filamentgroup/grunticon | (c) 2015 Scott Jehl, Filament Group, Inc. | MIT license. */
    
    (function(e){function t(t,n,r,o){"use strict";function a(){for(var e,n=0;u.length>n;n++)u[n].href&&u[n].href.indexOf(t)>-1&&(e=!0);e?i.media=r||"all":setTimeout(a)}var i=e.document.createElement("link"),l=n||e.document.getElementsByTagName("script")[0],u=e.document.styleSheets;return i.rel="stylesheet",i.href=t,i.media="only x",i.onload=o||null,l.parentNode.insertBefore(i,l),a(),i}var n=function(r,o){"use strict";if(r&&3===r.length){var a=e.navigator,i=e.Image,l=!(!document.createElementNS||!document.createElementNS("http://www.w3.org/2000/svg","svg").createSVGRect||!document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#Image","1.1")||e.opera&&-1===a.userAgent.indexOf("Chrome")||-1!==a.userAgent.indexOf("Series40")),u=new i;u.onerror=function(){n.method="png",n.href=r[2],t(r[2])},u.onload=function(){var e=1===u.width&&1===u.height,a=r[e&&l?0:e?1:2];n.method=e&&l?"svg":e?"datapng":"png",n.href=a,t(a,null,null,o)},u.src="",document.documentElement.className+=" grunticon"}};n.loadCSS=t,e.grunticon=n})(this);(function(e,t){"use strict";var n=t.document,r="grunticon:",o=function(e){if(n.attachEvent?"complete"===n.readyState:"loading"!==n.readyState)e();else{var t=!1;n.addEventListener("readystatechange",function(){t||(t=!0,e())},!1)}},a=function(e){return t.document.querySelector('link[href$="'+e+'"]')},c=function(e){var t,n,o,a,c,i,u={};if(t=e.sheet,!t)return u;n=t.cssRules?t.cssRules:t.rules;for(var l=0;n.length>l;l++)o=n[l].cssText,a=r+n[l].selectorText,c=o.split(");")[0].match(/US\-ASCII\,([^"']+)/),c&&c[1]&&(i=decodeURIComponent(c[1]),u[a]=i);return u},i=function(e){var t,o,a;o="data-grunticon-embed";for(var c in e)if(a=c.slice(r.length),t=n.querySelectorAll(a+"["+o+"]"),t.length)for(var i=0;t.length>i;i++)t[i].innerHTML=e[c],t[i].style.backgroundImage="none",t[i].removeAttribute(o);return t},u=function(t){"svg"===e.method&&o(function(){i(c(a(e.href))),"function"==typeof t&&t()})};e.embedIcons=i,e.getCSS=a,e.getIcons=c,e.ready=o,e.svgLoadedCallback=u,e.embedSVG=u})(grunticon,this);
                
                grunticon(["/theme/css/icons.data.svg.css", "/theme/css/icons.data.png.css", "/theme/css/icons.fallback.css"]);
            </script>
        <noscript><link href="/theme/css/icons.fallback.css" rel="stylesheet"></noscript>
        <!-- menu toggle javascript -->
        <script type="text/javascript">
            document.addEventListener("DOMContentLoaded", initMenu);
            
            function initMenu(){
                var menu = document.getElementById("menu");
                var menulink = document.getElementById("menu-link");
                menulink.addEventListener("click", function toggleMenu(){
                        window.event.preventDefault();
                        menulink.classList.toggle('active');
                        menu.classList.toggle('active');              
                    });
            };
        </script>
        <!-- 不蒜子 -->
        <script async src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>

    <meta name="description" content="<p>方法声明；指针接受者的方法；方法变量，方法表达式；接口类型；接口值：比较接口值，含有空指针的非空接口；类型断言；类型分支；使用接口的一些建议</p>" />

    <meta name="tags" content="gopl" />
  <!-- 替换部分base的样式，看文章时，要再宽一点，右边有很多空间可以撑开 -->
  <link rel="stylesheet" type="text/css" href="/theme/css/mycss/article.css" />

</head>
<body>
    <div role="banner" id="masthead">
        <header>
            <a href="/"><img src="/images/mowang.png" alt="McManus Logo"></a>
                <h1>来玩魔王的咚@骑士救兵</h1>
            <a href="#menu" id="menu-link">more stuff</a>
            <nav id="menu">
                <ul>
                        <li><a href="/tags">tags</a></li>
                            <li><a href="/category/cloud.html">Cloud</a></li>
                            <li><a href="/category/docker.html">Docker</a></li>
                            <li class="active"><a href="/category/go.html">Go</a></li>
                            <li><a href="/category/linux.html">Linux</a></li>
                            <li><a href="/category/python.html">Python</a></li>
                            <li><a href="/category/xue-xi-bi-ji.html">学习笔记</a></li>
                            <li><a href="/category/yun-wei-zi-dong-hua.html">运维自动化</a></li>
                </ul>
            </nav>
        </header>
    </div>
        <div class="page" role="main">
  <div class="article" role="article">
    <article>
        <footer>
            <a name="top"></a>
            <p>
              <time datetime=" 2020-07-14 17:00:00+08:00">
                <script>document.write(moment('2020-07-14 17:00:00+08:00').format('LL'));</script>
              </time>
              ~
              <time datetime=" 2020-07-14 17:00:00+08:00">
                <script>document.write(moment('2020-07-14 17:00:00+08:00').format('LL'));</script>
              </time>
            </p>
        </footer>
        <header>
          <h2>
            Go 方法和接口
          </h2>
        </header>
      <div class="content">
         <div class="toc">
<ul>
<li><a href="#fang-fa-sheng-ming">方法声明</a></li>
<li><a href="#zhi-zhen-jie-shou-zhe-de-fang-fa">指针接收者的方法</a></li>
<li><a href="#fang-fa-bian-liang-yu-fang-fa-biao-da-shi">方法变量与方法表达式</a><ul>
<li><a href="#fang-fa-bian-liang-method-value">方法变量（method value）</a></li>
<li><a href="#fang-fa-biao-da-shi-method-expression">方法表达式（method expression）</a></li>
</ul>
</li>
<li><a href="#jie-kou-lei-xing">接口类型</a></li>
<li><a href="#jie-kou-zhi">接口值</a><ul>
<li><a href="#bi-jiao-jie-kou-zhi">比较接口值</a></li>
<li><a href="#zhu-yi-han-you-kong-zhi-zhen-de-fei-kong-jie-kou">注意：含有空指针的非空接口</a></li>
</ul>
</li>
<li><a href="#lei-xing-duan-yan">类型断言</a><ul>
<li><a href="#ju-ti-lei-xing">具体类型</a></li>
<li><a href="#jie-kou-lei-xing_1">接口类型</a></li>
</ul>
</li>
<li><a href="#lei-xing-fen-zhi">类型分支</a></li>
<li><a href="#shi-yong-jie-kou-de-yi-xie-jian-yi">使用接口的一些建议</a></li>
</ul>
</div>
<h3 id="fang-fa-sheng-ming"><a class="toclink" href="#fang-fa-sheng-ming">方法声明</a></h3>
<p>写一个简单的方法：</p>
<div class="highlight"><pre><span></span><code><span class="kd">type</span><span class="w"> </span><span class="nx">Point</span><span class="w"> </span><span class="kd">struct</span><span class="p">{</span><span class="nx">X</span><span class="p">,</span><span class="w"> </span><span class="nx">Y</span><span class="w"> </span><span class="kt">float64</span><span class="p">}</span><span class="w"></span>

<span class="c1">// 普通的函数</span><span class="w"></span>
<span class="kd">func</span><span class="w"> </span><span class="nx">Distance</span><span class="p">(</span><span class="nx">p</span><span class="p">,</span><span class="w"> </span><span class="nx">q</span><span class="w"> </span><span class="nx">Point</span><span class="p">)</span><span class="w"> </span><span class="kt">float64</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="nx">math</span><span class="p">.</span><span class="nx">Hypot</span><span class="p">(</span><span class="nx">q</span><span class="p">.</span><span class="nx">X</span><span class="o">-</span><span class="nx">p</span><span class="p">.</span><span class="nx">X</span><span class="p">,</span><span class="w"> </span><span class="nx">q</span><span class="p">.</span><span class="nx">Y</span><span class="o">-</span><span class="nx">p</span><span class="p">.</span><span class="nx">Y</span><span class="p">)</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>

<span class="c1">// 同样的作用，用方法实现</span><span class="w"></span>
<span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">p</span><span class="w"> </span><span class="nx">Point</span><span class="p">)</span><span class="w"> </span><span class="nx">Distance</span><span class="p">(</span><span class="nx">q</span><span class="w"> </span><span class="nx">Point</span><span class="p">)</span><span class="w"> </span><span class="kt">float64</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="nx">math</span><span class="p">.</span><span class="nx">Hypot</span><span class="p">(</span><span class="nx">q</span><span class="p">.</span><span class="nx">X</span><span class="o">-</span><span class="nx">p</span><span class="p">.</span><span class="nx">X</span><span class="p">,</span><span class="w"> </span><span class="nx">q</span><span class="p">.</span><span class="nx">Y</span><span class="o">-</span><span class="nx">p</span><span class="p">.</span><span class="nx">Y</span><span class="p">)</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>

<p><strong>接收者</strong>：附加的参数 p 称为方法的接收者。  </p>
<p>调用方法的时候，接收者在方法名的前面。这样就和声明保持一致：</p>
<div class="highlight"><pre><span></span><code><span class="nx">p</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">Point</span><span class="p">{</span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="mi">2</span><span class="p">}</span><span class="w"></span>
<span class="nx">q</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">Point</span><span class="p">{</span><span class="mi">4</span><span class="p">,</span><span class="w"> </span><span class="mi">6</span><span class="p">}</span><span class="w"></span>
<span class="nx">fmt</span><span class="p">.</span><span class="nx">Println</span><span class="p">(</span><span class="nx">Distance</span><span class="p">(</span><span class="nx">p</span><span class="p">,</span><span class="w"> </span><span class="nx">q</span><span class="p">))</span><span class="w"> </span><span class="c1">// 函数调用</span><span class="w"></span>
<span class="nx">fmt</span><span class="p">.</span><span class="nx">Println</span><span class="p">(</span><span class="nx">p</span><span class="p">.</span><span class="nx">Distance</span><span class="p">(</span><span class="nx">q</span><span class="p">))</span><span class="w">  </span><span class="c1">// 方法调用</span><span class="w"></span>
</code></pre></div>

<p><strong>选择子</strong>：表达是 p.Distance 称作选择子（selector），因为它为接收者 p 选择合适的 Distance 方法。  </p>
<h3 id="zhi-zhen-jie-shou-zhe-de-fang-fa"><a class="toclink" href="#zhi-zhen-jie-shou-zhe-de-fang-fa">指针接收者的方法</a></h3>
<p>对于函数，它会复制每一只实参变量。如果函数需要更新一个变量，或者是因为实参太大而需要避免复制整个实参，就需要使用指针来传递变量的地址。<br>
对于方法的接受者，也可以将方法绑定到指针类型。习惯上遵循如果一个类型的任何一个方法使用指针接收者，那么所有该类型的方法都应该使用指针接收者，即使有些方法不一定需要。<br>
另外，为了防止混淆，不允许本身是指针的类型进行方法声明，会有编译错误：</p>
<div class="highlight"><pre><span></span><code><span class="kd">type</span><span class="w"> </span><span class="nx">p</span><span class="w"> </span><span class="o">*</span><span class="kt">int</span><span class="w"></span>
<span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">p</span><span class="p">)</span><span class="w"> </span><span class="nx">f</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="cm">/*...*/</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="c1">// 编译错误：非法的接收者类型</span><span class="w"></span>
</code></pre></div>

<h3 id="fang-fa-bian-liang-yu-fang-fa-biao-da-shi"><a class="toclink" href="#fang-fa-bian-liang-yu-fang-fa-biao-da-shi">方法变量与方法表达式</a></h3>
<p>方法变量也有方法值的翻译，可能方法值更通用？  </p>
<h4 id="fang-fa-bian-liang-method-value"><a class="toclink" href="#fang-fa-bian-liang-method-value">方法变量（method value）</a></h4>
<p>通常是在相同的表达式里使用和调用方法，但是把两个操作分开也是可以的。选择子 p.Distance 可以赋予一个<strong>方法变量</strong>，它是一个函数，把方法(Point.Distance)绑定到一个接收者 p 上。<br>
函数只需要提供实参而不需要提供接收者就能够调用：</p>
<div class="highlight"><pre><span></span><code><span class="nx">p</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">Point</span><span class="p">{</span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="mi">2</span><span class="p">}</span><span class="w"></span>
<span class="nx">q</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">Point</span><span class="p">{</span><span class="mi">4</span><span class="p">,</span><span class="w"> </span><span class="mi">6</span><span class="p">}</span><span class="w"></span>
<span class="nx">distanceFromP</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">p</span><span class="p">.</span><span class="nx">Distance</span><span class="w"> </span><span class="c1">// 方法变量</span><span class="w"></span>
<span class="nx">fmt</span><span class="p">.</span><span class="nx">Println</span><span class="p">(</span><span class="nx">distanceFromP</span><span class="p">(</span><span class="nx">q</span><span class="p">))</span><span class="w"></span>
</code></pre></div>

<p>这里 p.Distance 是选择子，把它赋值给变量 distanceFromP，这个变量就是方法变量，并且这个变量是一个函数。  </p>
<p>如果包内的 API 调用一个函数值，并且使用者期望这个函数的行为是调用一个特定接收者的方法，方法变量就非常有用。<br>
使用方法变量还可以是代码更加简洁：</p>
<div class="highlight"><pre><span></span><code><span class="kd">type</span><span class="w"> </span><span class="nx">Rocket</span><span class="w"> </span><span class="kd">struct</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="cm">/* ... */</span><span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">r</span><span class="w"> </span><span class="o">*</span><span class="nx">Rocket</span><span class="p">)</span><span class="w"> </span><span class="nx">Launch</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="cm">/* ... */</span><span class="w"> </span><span class="p">}</span><span class="w"></span>

<span class="nx">r</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">new</span><span class="p">(</span><span class="nx">Rocket</span><span class="p">)</span><span class="w"></span>
<span class="nx">time</span><span class="p">.</span><span class="nx">AfterFunc</span><span class="p">(</span><span class="mi">10</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nx">Second</span><span class="p">,</span><span class="w"> </span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nx">Launch</span><span class="p">()</span><span class="w"> </span><span class="p">})</span><span class="w"> </span><span class="c1">// 如果没有方法变量，那么要把执行一个方法包在一个函数里，等到函数被调用后执行</span><span class="w"></span>
<span class="nx">time</span><span class="p">.</span><span class="nx">AfterFunc</span><span class="p">(</span><span class="mi">10</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nx">Second</span><span class="p">,</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nx">Launch</span><span class="p">)</span><span class="w">  </span><span class="c1">// 使用方法变量，这里 r.Launch 就是一个函数，只是没有赋值给某个变量，没有函数名</span><span class="w"></span>
</code></pre></div>

<p>函数 time.AfterFunc 的作用是在指定的延迟后调用一个函数。上面说了，方法变量也是函数。  </p>
<h4 id="fang-fa-biao-da-shi-method-expression"><a class="toclink" href="#fang-fa-biao-da-shi-method-expression">方法表达式（method expression）</a></h4>
<p>调用方法的时候必须提供接收者，并且按照选择子的语法进行调用。<br>
<strong>方法表达式</strong>，写成 T.f 或者 (*T).f。<br>
其中 T 是类型，是一种函数变量，把原来方法的接收者替换成函数的第一个形参。<br>
因此它可以像平常的函数一样调用：</p>
<div class="highlight"><pre><span></span><code><span class="nx">p</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">Point</span><span class="p">{</span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="mi">2</span><span class="p">}</span><span class="w"></span>
<span class="nx">q</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">Point</span><span class="p">{</span><span class="mi">4</span><span class="p">,</span><span class="w"> </span><span class="mi">6</span><span class="p">}</span><span class="w"></span>
<span class="nx">distance</span><span class="w"> </span><span class="o">:=</span><span class="w">  </span><span class="nx">Point</span><span class="p">.</span><span class="nx">Distance</span><span class="w">  </span><span class="c1">// 方法表达式</span><span class="w"></span>
<span class="nx">fmt</span><span class="p">.</span><span class="nx">Println</span><span class="p">(</span><span class="nx">distance</span><span class="p">(</span><span class="nx">p</span><span class="p">,</span><span class="w"> </span><span class="nx">q</span><span class="p">))</span><span class="w"></span>
<span class="nx">fmt</span><span class="p">.</span><span class="nx">Printf</span><span class="p">(</span><span class="s">&quot;%T\n&quot;</span><span class="p">,</span><span class="w"> </span><span class="nx">distance</span><span class="p">)</span><span class="w"> </span><span class="c1">// &quot;func(Point, Point) float64&quot;</span><span class="w"></span>
</code></pre></div>

<p>如果需要一个值来代表多个方法中的一个，而方法都属于同一个类型，方法表达式可以实现让这个值所对应的方法来处理不同的接收者。<em>就是可以把一个方法变成一个函数，函数的变量会增加一个，第一个变量就是原来方法中的接收者。其实各个参数的顺序还是一样的，原本第一个参数在 func 前，现在移动到了 func 后面。 p.Distance(q) 变成了 distance(p, q)。</em>  </p>
<h3 id="jie-kou-lei-xing"><a class="toclink" href="#jie-kou-lei-xing">接口类型</a></h3>
<p>io包定义了很多有用的接口：</p>
<ul>
<li>io.Writer ： 抽象了所有写入字节的类型，下面会列举</li>
<li>io.Reader ： 抽象了所有可以读取字节的类型</li>
<li>io.Closer ： 抽象了所有可以关闭的类型，比如文件或者网络连接</li>
</ul>
<p>io.Writer 是一个广泛使用的接口，它负责所有可以写入字节的抽象，包括但不限于下面列举的这些：</p>
<ul>
<li>文件</li>
<li>内存缓冲区</li>
<li>网络连接</li>
<li>HTTP客户端</li>
<li>打包器（archiver）</li>
<li>散列器（hasher）</li>
</ul>
<h3 id="jie-kou-zhi"><a class="toclink" href="#jie-kou-zhi">接口值</a></h3>
<p><strong>接口值</strong>，就是一个接口类型的值。分两个部分：</p>
<ul>
<li><strong>动态类型</strong>： 该接口的具体类型</li>
<li><strong>动态值</strong>： 该具体类型的一个值</li>
</ul>
<div class="highlight"><pre><span></span><code><span class="kd">var</span><span class="w"> </span><span class="nx">w</span><span class="w"> </span><span class="nx">io</span><span class="p">.</span><span class="nx">Writer</span><span class="w">  </span><span class="c1">// 声明接口，动态类型和动态值都是nil</span><span class="w"></span>
<span class="nx">w</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">os</span><span class="p">.</span><span class="nx">Stdout</span><span class="w">  </span><span class="c1">// 有动态类型，也有动态值</span><span class="w"></span>
<span class="nx">w</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">io</span><span class="p">.</span><span class="nx">Writer</span><span class="p">(</span><span class="nx">os</span><span class="p">.</span><span class="nx">Stdout</span><span class="p">)</span><span class="w">  </span><span class="c1">// 和上面这句等价，把一个具体类型显式转换为接口类型</span><span class="w"></span>
<span class="nx">w</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nb">new</span><span class="p">(</span><span class="nx">bytes</span><span class="p">.</span><span class="nx">Buffer</span><span class="p">)</span><span class="w">  </span><span class="c1">// 有动态类型，也有动态值</span><span class="w"></span>
<span class="nx">w</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="kc">nil</span><span class="w">  </span><span class="c1">// 把动态类型和动态值都设置为nil，恢复到声明时的状态</span><span class="w"></span>
</code></pre></div>

<h4 id="bi-jiao-jie-kou-zhi"><a class="toclink" href="#bi-jiao-jie-kou-zhi">比较接口值</a></h4>
<p>接口值可以用 == 和 != 来比较。动态类型一致，然后动态值相等（使用动态类型的 == 来比较），那么接口值相等。接口值都是nil也是相等的。<br>
<strong>可以作为map的key</strong>，也可以作为switch语句的操作数，因为可以比较。<br>
动态值可能是不可比较的类型，比如切片。对这样的接口进行比较，就会Panic。把这样的接口用作map的key或者switch语句的操作数时也同样会Panic。所以，仅在能确认接口值包含的动态值可以比较时，才比较接口值。<br>
fmt 包的 %T 打印出来的就是动态类型。在内部实现中，fmt 用反射来拿到接口动态类型的名字。  </p>
<h4 id="zhu-yi-han-you-kong-zhi-zhen-de-fei-kong-jie-kou"><a class="toclink" href="#zhu-yi-han-you-kong-zhi-zhen-de-fei-kong-jie-kou">注意：含有空指针的非空接口</a></h4>
<p>空的接口值（动态类型和动态值都为空）和仅仅动态值为nil的接口值是不一样的。</p>
<div class="highlight"><pre><span></span><code><span class="kd">const</span><span class="w"> </span><span class="nx">debug</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="kc">true</span><span class="w"></span>

<span class="kd">func</span><span class="w"> </span><span class="nx">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">    </span><span class="kd">var</span><span class="w"> </span><span class="nx">buf</span><span class="w"> </span><span class="o">*</span><span class="nx">bytes</span><span class="p">.</span><span class="nx">Buffer</span><span class="w"></span>
<span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nx">debug</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">        </span><span class="nx">buf</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nb">new</span><span class="p">(</span><span class="nx">bytes</span><span class="p">.</span><span class="nx">Buffer</span><span class="p">)</span><span class="w"></span>
<span class="w">    </span><span class="p">}</span><span class="w"></span>
<span class="w">    </span><span class="nx">f</span><span class="p">(</span><span class="nx">buf</span><span class="p">)</span><span class="w"></span>
<span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nx">debug</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">        </span><span class="c1">// ...使用 buf...</span><span class="w"></span>
<span class="w">    </span><span class="p">}</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>

<span class="c1">// 如果 out 不是 nil，那么会向其写入输出的数据</span><span class="w"></span>
<span class="kd">func</span><span class="w"> </span><span class="nx">f</span><span class="p">(</span><span class="nx">out</span><span class="w"> </span><span class="nx">io</span><span class="p">.</span><span class="nx">Writer</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">    </span><span class="c1">// ...其他代码...</span><span class="w"></span>
<span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nx">out</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">        </span><span class="nx">out</span><span class="p">.</span><span class="nx">Write</span><span class="p">([]</span><span class="nb">byte</span><span class="p">(</span><span class="s">&quot;done\n&quot;</span><span class="p">))</span><span class="w"></span>
<span class="w">    </span><span class="p">}</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>

<p>这里，把一个类型为 *bytes.Buffer 的空指针赋给了 out 参数，此时 out 的动态值为空。但它的动态类型是 *bytes.Buffer。就是说 out 是一个包含空指针的非空接口，所以这里的检查 <code>out != nil</code> 是 true，防御不了这种情况。<br>
对于某些类型，比如 *os.File，空接收值是合法的。但是对于这里的 *buyes.Buffer，要求接收者不能为空，于是运行时会Panic。<br>
这里的解决方案是，把 main 函数中的 buf 类型修改为 io.Writer，从而避免在最开始就把一个功能不完整的值赋给一个接口：</p>
<div class="highlight"><pre><span></span><code><span class="kd">var</span><span class="w"> </span><span class="nx">buf</span><span class="w"> </span><span class="nx">io</span><span class="p">.</span><span class="nx">Writer</span><span class="w"></span>
<span class="k">if</span><span class="w"> </span><span class="nx">debug</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">    </span><span class="nx">buf</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nb">new</span><span class="p">(</span><span class="nx">bytes</span><span class="p">.</span><span class="nx">Buffer</span><span class="p">)</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="nx">f</span><span class="p">(</span><span class="nx">buf</span><span class="p">)</span><span class="w"></span>
</code></pre></div>

<h3 id="lei-xing-duan-yan"><a class="toclink" href="#lei-xing-duan-yan">类型断言</a></h3>
<p>类型断言是一个作用在接口值上的操作，代码类似于<code>x.(T)</code>，x是一个接口类型的表达式，而T是一个类型（称为断言类型）。类型断言会检查操作数的动态类型是否满足指定的断言类型。<br>
这里有两种可能：</p>
<ul>
<li>断言类型T是一个具体类型</li>
<li>断言类型T是一个接口类型</li>
</ul>
<p>类型断言可以返回两个结果，此时操作不会因为检查失败而崩溃。多出来的返回值是一个布尔型，用来指示断言是否成功。按照惯例，一般变量名用ok。如果操作失败，ok为false，而第一个返回值会是断言类型的零值。  </p>
<h4 id="ju-ti-lei-xing"><a class="toclink" href="#ju-ti-lei-xing">具体类型</a></h4>
<p>如果断言类型T是一个具体类型，断言类型会检查x的动态类型是否就是T。如果检查成功，返回x的动态值，返回的类型就是T。如果检查失败，那么操作崩溃。  </p>
<h4 id="jie-kou-lei-xing_1"><a class="toclink" href="#jie-kou-lei-xing_1">接口类型</a></h4>
<p>如果断言类型T是一个接口类型，断言类型会检查x的动态类型是否满足T。如果检查成功，动态值并没有提取出来，仍然是一个接口值，接口值的类型和值部分也不会变，只是结果类型为接口类型T。就是说，这里类型断言就是一个接口值表达式，从一个接口类型变为拥有另外一套方法的接口类型，但保留了接口值中动态类型和动态值部分。如果检查失败还是会崩溃。  </p>
<h3 id="lei-xing-fen-zhi"><a class="toclink" href="#lei-xing-fen-zhi">类型分支</a></h3>
<p>先扯一下接口，接口有两种不同的风格。<br>
第一种风格下，典型的比如：io.Reader、io.Writer、fmt.Stringer、sort.Interface、http.Handler 和 error。接口上的各种方法突出了满足这个接口的具体类型之间的相似性，但隐藏了各个具体类型的布局和各自特有的功能。这种风格强调了方法，而不是具体类型。<br>
第二种风格则充分利用了接口值能够容纳各种具体类型的能力，它把接口作为这些类型的<strong>联合</strong>（union）来使用。类型断言用来在运行时区分这些类型并分别处理。这这种风格中，强调的是满足这个接口的具体类型，而不是这个接口的方法（经常是没变方法的空接口），也不注重信息隐藏。这种风格的接口使用方式称为<strong>可识别联合</strong>（discriminated union）。  </p>
<p>如果对面向对象熟悉，这两种风格分别对应：</p>
<ul>
<li><strong>子类型多态</strong>（subtype polymorphism）</li>
<li><strong>特设多态</strong>（ad hoc polymorphism）</li>
</ul>
<p><strong>类型分支</strong><br>
类型断言（判断是不是某一种类型），加上多个if-else链重复判断，就可以断言多个类型。这里的类型分支简化了这种if-else链。<br>
类型分支（判断是否是某一些类型中的一种），获取到类型的动态值，执行不同的操作。<br>
代码类似于<code>x.(type)</code>，它使用了关键词字面量type。</p>
<div class="highlight"><pre><span></span><code><span class="k">switch</span><span class="w"> </span><span class="nx">x</span><span class="p">.(</span><span class="kd">type</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">    </span><span class="k">case</span><span class="w"> </span><span class="kc">nil</span><span class="p">:</span><span class="w">       </span><span class="c1">// ...</span><span class="w"></span>
<span class="w">    </span><span class="k">case</span><span class="w"> </span><span class="kt">int</span><span class="p">,</span><span class="w"> </span><span class="kt">uint</span><span class="p">:</span><span class="w"> </span><span class="c1">// ...</span><span class="w"></span>
<span class="w">    </span><span class="k">case</span><span class="w"> </span><span class="kt">bool</span><span class="p">:</span><span class="w">      </span><span class="c1">// ...</span><span class="w"></span>
<span class="w">    </span><span class="k">case</span><span class="w"> </span><span class="kt">string</span><span class="p">:</span><span class="w">    </span><span class="c1">// ...</span><span class="w"></span>
<span class="w">    </span><span class="k">default</span><span class="p">:</span><span class="w">        </span><span class="c1">// ...</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>

<p>这里用到了x.(type)，这个必须与switch case联合使用，如果写在switch外面，编译器会报错。  </p>
<h3 id="shi-yong-jie-kou-de-yi-xie-jian-yi"><a class="toclink" href="#shi-yong-jie-kou-de-yi-xie-jian-yi">使用接口的一些建议</a></h3>
<p>不要一开始就定义接口，每个接口却只是一个单独的实现。这种接口是不必要的抽象，还会有运行时的成本。仅在有两个或多个具体类型需要按统一的方式处理时才需要接口。<br>
上面的建议也有特例，如果接口和类型实现出于依赖的原因不能放在同一个包里边，那么一个接口只有一个具体类型实现也是可以的。在这种情况下，接口是一种解耦两个包的好方式。  </p>
      </div>
      <div class="back-to-top">
        <a href="/">HOME</a>
        <a href="#top">TOP</a>
      </div>
    </article>
  </div>
<!-- end article -->
<!-- 页面往下滚动一段之后才会显示TOC -->
<script>
  window.onscroll = function() {
    var tocbox = document.getElementsByClassName('toc')[0];
    var osTop = document.documentElement.scrollTop || document.body.scrollTop;
    var osWidth = document.documentElement.scrollWidth || document.body.scrollWidth;
    // console.log(osTop)
    if (osTop>300 && osWidth>865) {
      tocbox.style.display = "block"
    }
    if (osTop<300 || osWidth<865) {
      tocbox.style.display = "none"
    }
  }
</script>
                <footer>
                    <div class="icons">
                    </div>
                    <span id="busuanzi_container_page_pv" style="padding: 10px">本文阅读量<span id="busuanzi_value_page_pv"></span>次</span>
                    <span id="busuanzi_container_site_pv" style="padding: 10px">本站总访问量<span id="busuanzi_value_site_pv"></span>次</span>
                    <span id="busuanzi_container_site_uv" style="padding: 10px">本站总访客数<span id="busuanzi_value_site_uv"></span>人</span>
                    <p>© <script>document.write(moment().format('YYYY'));</script> 749B</p>
                </footer>
        </div>
</body>
</html>